/**
 * Created with IntelliJ IDEA.
 * User: zhengry
 * Date: 2014/11/18
 * Time: 15:54
 */
define([
    'require',
    'angular',
    'jquery'
], function (require, angular, $) {
    return function ($http, $window, $compile, I18NLoader, ControlValidatorService, Arrays) {

        /**
         * @cfg params.$form {jQuery()} 表单元素
         * @cfg params.entityName {String} 实体名称
         * @cfg params.defaultIllegalTarget {String} 默认错误消息输出位置
         * @constructor
         */
        function FormValidatorController(params) {
            angular.extend(this, params);
        }

        FormValidatorController.prototype.setRules = function (rules) {
            this.rules = rules;
        };

        FormValidatorController.prototype.getFieldRules = function (fieldName) {
            var predicate = function (prop) {
                var propTokens;
                if (prop === fieldName) {
                    return true;
                } else {
                    propTokens = prop.split('.');
                    if (propTokens.length > 1) {
                        return new RegExp(propTokens.join('(\\\[\\\d+\\\])?\.')).test(fieldName);
                    }
                }
                return false;
            };
            return Arrays.filter(this.rules, function (rule) {
                if (!!rule.property) {
                    return predicate(rule.property);
                } else {
                    return Arrays.exists(rule.properties, predicate);
                }
            });
        };

        FormValidatorController.prototype.setEntityI18n = function (entityI18n) {
            this.entityI18n = entityI18n;
        };

        FormValidatorController.prototype.setForm = function (form) {
            this.form = form;
        };

        FormValidatorController.prototype.forEachFieldValidator = function (scope, callback) {
            var me = this;
            angular.forEach(me.form, function (field, fieldName) {
                if (/^[^\$]/.test(fieldName) && !angular.isFunction(field)) {
                    var fieldRules = me.getFieldRules(fieldName);
                }
            });
        };

        /**
         *
         * @cfg params.gValidator {FormValidatorController} 表单校验器
         * @cfg params.fieldName {String} 字段名称
         * @cfg params.field {ngModelController} 字段
         * @cfg params.rules {[Object]} 校验规则
         * @constructor
         */
        function FieldValidator(params) {

        }


        /**
         * @param scope
         * @param callback
         * @param [rules]
         */
        FormValidatorController.prototype.forEachRulesValidator = function (scope, callback, rules) {
            var me = this;
            angular.forEach(rules || me.rules, function (rule) {
                var ruleValidator = new RuleValidator({
                    rule: rule,
                    scope: scope,
                    form: me.form,
                    $form: me.$form,
                    defaultIllegalTarget: me.defaultIllegalTarget,
                    entityName: me.entityName,
                    entityI18n: me.entityI18n
                });

                callback(ruleValidator);
            });
        };

        /**
         *
         * @param params.form {ngForm} 表单控制器
         * @param params.scope {Object} 指令的scope
         * @param params.rule {[Object]} 当前字段的校验规则
         * @param params.entityName {String} 实体名称
         * @param params.entityI18n {String} 实体名称
         * @constructor
         */
        function RuleValidator(params) {
            angular.extend(this, params);
            this.applyValidator();
            this.addIllegalMessage();
        }

        RuleValidator.prototype.applyValidator = function () {
            if (ControlValidatorService.isSupported(this.rule.ruleName)) {
                var controlValidator = ControlValidatorService.from(this);
                controlValidator.apply();
                // 为规则添加匹配控件的属性，
                this.matchedFields = controlValidator.matchedControls;
            }
        };

        RuleValidator.prototype.addIllegalMessage = function () {
            var me = this,
                rule = me.rule,
                entityI18n = me.entityI18n,
                matchedFields = me.matchedFields,
                localIllegalMessage = entityI18n[rule['message']] || '';

            me.illegalMessage = {};
            angular.forEach(matchedFields, function (matchedField) {
                var fieldName;
                if (matchedField) {
                    fieldName = matchedField.$name;
                    if (!me.illegalMessage[fieldName]) {
                        me.illegalMessage[fieldName] = {};
                    }
                    me.illegalMessage[fieldName] = localIllegalMessage;
                }
            });
        };

        /**
         * @param [$control] 控件
         */
        RuleValidator.prototype.getIllegalMessage = function ($control) {
            var fieldName;
            if ($control) {
                fieldName = $control.attr('name');
                if (fieldName) {
                    var illegalMessage = {};
                    illegalMessage[fieldName] = this.illegalMessage[fieldName];
                    return illegalMessage;
                }
            }
            return this.illegalMessage;
        };


        /**
         * @param [element] {jQuery} 当前scope
         */
        RuleValidator.prototype.addRuleHelpBlocks = function (element, ngModel) {
            var me = this,
                form = me.form,
                illegalMessage = me.getIllegalMessage(element);
            angular.forEach(illegalMessage, function (localIllegalMessage, fieldName) {
                var fieldEle = element || me.getFieldEleByFieldName(fieldName),
                    helpBlockParams, illegalTarget;
                if (!!fieldEle) {
                    illegalTarget = fieldEle.data('invalid-msg') || me.defaultIllegalTarget;
                    helpBlockParams = {
                        element: fieldEle,
                        localIllegalMessage: localIllegalMessage,
                        fieldName: fieldName,
                        field: form[fieldName]
                    };
                    switch (illegalTarget) {
                        case 'tooltip':
                            me.addTooltipHelpBlock(helpBlockParams);
                            break;
                        case ':next':
                            me.addNextBoxHelpBlock(helpBlockParams);
                            break;
                        case 'bs3-horizontal':
                            me.addBs3HorizontalMsg(helpBlockParams);
                            break;
                        default :
                            if (!!illegalTarget) {
                                me.addSelectorErrorMsg(helpBlockParams, illegalTarget);
                            }
                            break;
                    }
                }
            });
        };

        RuleValidator.prototype.getFieldEleByFieldName = function (fieldName) {
            var element = this.$form.find('[name=\'' + fieldName + '\']');
            if (element.length > 1) {
                // TODO
                return element.closest('[valid-display]');
            } else if (element.length === 1) {
                return element;
            }
        };

        RuleValidator.prototype.addTooltipHelpBlock = function (params) {
            var me = this;
            require([
                'css!' + window.ctx + '/bower_components/tooltipster/css/tooltipster.css',
                'css!' + window.ctx + '/bower_components/tooltipster/css/themes/tooltipster-noir.css',
                'tooltipster'
            ], function () {
                var scope = me.scope,
                    formName = me.form.$name,
                    element = params.element,
                    fieldName = params.fieldName,
                    field = params.field,
                    ruleName = me.rule.ruleName,
                    localIllegalMessage = params.localIllegalMessage,
                    ngShowPrefix = formName + '[\'' + fieldName + '\']',
                    ruleHelpBlockShowExpress = ngShowPrefix + '.$error.' + ruleName,
                    $tooltipContrl = element.attr('type') === 'hidden' ? element.closest('span') : element,
                    $ruleIllegalMsgBlock = $('<div></div>', {text: localIllegalMessage, 'ng-show': ruleHelpBlockShowExpress}),
                    ns = $tooltipContrl.data('tooltipster-ns'),
                    $target;

                $compile($ruleIllegalMsgBlock)(scope);

                if (ns) {
                    $target = $tooltipContrl.tooltipster('content');
                    $target.append($ruleIllegalMsgBlock);
                } else {
                    $target = $ ('<span style="color:red;"></span>').append($ruleIllegalMsgBlock);
                    $tooltipContrl.tooltipster({
                        position: 'right',
                        trigger: 'custom',
                        theme: 'tooltipster-noir',
                        contentAsHTML: true,
                        contentCloning: false,
                        content: $target
                    });
                }


                scope.$watch(formName + '[\'' + fieldName + '\'].$invalid', function (invalid) {
                    if (invalid) {
                        $tooltipContrl.tooltipster('show');
                    } else {
                        $tooltipContrl.tooltipster('hide');
                    }
                });

                addVerifyFn(field, element, scope);
            })
        };
        RuleValidator.prototype.addNextBoxHelpBlock = function (params) {
            var me = this,
                scope = me.scope,
                formName = me.form.$name,
                element = params.element,
                fieldName = params.fieldName,
                ruleName = me.rule.ruleName,
                field = params.field,
                localIllegalMessage = params.localIllegalMessage,
                $controlBox = element.parent(),
                ngShowPrefix = formName + '[\'' + fieldName + '\']',
                helpBlockBoxShowExpress = ngShowPrefix + '.$invalid',
                ruleHelpBlockShowExpress = ngShowPrefix + '.$error.' + ruleName,
                $target, $ruleTarget;

            // checkbox, radio, 一组控件时， 取父元素
            while ($controlBox.length > 1) {
                $controlBox = $controlBox.parent();
            }
            $target = $(':last-child', $controlBox);
            if ($target.not('div')) {
                $target = $('<div>', {'class': 'ng-cloak', css: {color: 'red'}, 'ng-show': helpBlockBoxShowExpress}).appendTo($controlBox);
            } else if ($target.attr('ng-show') !== helpBlockBoxShowExpress) {
                $target.attr('ng-show', helpBlockBoxShowExpress);
            }
            $ruleTarget = $target.find('div[ng-show="' + ruleHelpBlockShowExpress + '"]');
            if ($ruleTarget.length === 0) {
                $('<div>', {'ng-show': ruleHelpBlockShowExpress, text: localIllegalMessage}).appendTo($target);
            }
            $compile($target)(scope);
            addVerifyFn(field, element, scope);
        };

        RuleValidator.prototype.addBs3HorizontalMsg = function (params) {
            var me = this,
                scope = me.scope,
                formName = me.form.$name,
                element = params.element,
                fieldName = params.fieldName,
                field = params.field,
                localIllegalMessage = params.localIllegalMessage,
                $formGroup = element.closest('div.form-group'),
                $controlBox = element.closest('div'),
                $helpBlock = $controlBox.children('.help-block'),
                ngShowPrefix = formName + '[\'' + fieldName + '\']',
                helpBlockBoxShowExpress = ngShowPrefix + '.$invalid',
                ruleName = me.rule.ruleName,
                ruleHelpBlockShowExpress = ngShowPrefix + '.$error.' + ruleName,
                $ruleTarget;
            // checkbox, radio, 一组控件时， 取父元素
            if ($helpBlock.length === 0) {
                $helpBlock = $('<span>', {'class': 'help-block ng-cloak', 'ng-show': helpBlockBoxShowExpress}).appendTo($controlBox);
            } else if ($helpBlock.attr('ng-show') !== helpBlockBoxShowExpress) {
                $helpBlock.attr('ng-show', helpBlockBoxShowExpress);
            }

            addRequireIconIfNotBlankOrNotNull(ruleName, $formGroup);

            $ruleTarget = $helpBlock.find('div[ng-show="' + ruleHelpBlockShowExpress + '"]');
            if ($ruleTarget.length === 0) {
                $('<span>', {'class': 'ng-cloak', 'ng-show': ruleHelpBlockShowExpress, text: localIllegalMessage}).appendTo($helpBlock);
            }
            $compile($helpBlock)(scope);
            addVerifyFn(field, element, scope);
        };

        RuleValidator.prototype.addSelectorErrorMsg = function (params, illegalTarget) {
            var me = this,
                scope = me.scope,
                formName = me.form.$name,
                element = params.element,
                localIllegalMessage = params.localIllegalMessage,
                fieldName = params.fieldName,
                ngShowPrefix = formName + '[\'' + fieldName + '\']',
                field = params.field,
                $target = angular.element(illegalTarget),
                ruleName = me.rule.ruleName,
                ruleHelpBlockShowExpress = ngShowPrefix + '.$error.' + ruleName;

            if ($target.length !== 1) {
                return;
            }

            var $ruleTarget = $target.find('div[ng-show="' + ruleHelpBlockShowExpress + '"]');
            if ($ruleTarget.length === 0) {
                $('<div>', {'ng-show': ruleHelpBlockShowExpress, text: localIllegalMessage}).appendTo($target);
            }
            $compile($target)(scope);

            addVerifyFn(field, element, scope);
        };

        function addRequireIconIfNotBlankOrNotNull(ruleName, $formGroup) {
            if (ruleName === 'notNull' || ruleName === 'require') {
                var $controlLabel = $formGroup.find('label:eq(0)');
                if ($('span.lbl-require', $controlLabel).length === 0) {
                    $controlLabel.wrapInner('<span class="lbl-require"></span>')
                }
            }
        }

        function addVerifyFn(field, $control, scope) {
            if (!field) return;
            field.verify = function () {
                var $parsers, $parser, i, len;
                $parsers = this.$parsers;
                if (!!$parsers) {
                    for (i = 0, len = $parsers.length; i < len; i++) {
                        $parser = $parsers[i];
                        $parser(this.$viewValue);
                    }
                }
            };

            if ($control.data('verify') === true) {
                field.verify();
            }

            $control.blur(function () {
                field.verify();
                if (!scope.$$phase) {
                    scope.$digest();
                }
            });
        }

        return {
            restrict: 'A',
            require: ['form', 'gValidator'],
            scope: false,
            link: function (scope, element, attrs, controllers) {
                var form = controllers[0],
                    gValidator = controllers[1],
                    entityAndGroupName = attrs['gValidator'],
                    entityName = entityAndGroupName.split('_')[0],
                    defaultIllegalTarget = attrs['invalidMsg'];

                if (!entityAndGroupName) {
                    throw new ReferenceError("require entityAndGroupName");
                }
                require(['validation/generated/' + entityAndGroupName, 'i18n!nls/entity-' + entityAndGroupName], function (validationData, entityI18n) {
                    var rules = validationData.rules;

                    gValidator.setForm(form);
                    gValidator.setRules(rules);
                    gValidator.setEntityI18n(entityI18n);

                    gValidator.forEachRulesValidator(scope, function (ruleValidator) {
                        ruleValidator.addRuleHelpBlocks();
                    });
                    form.verify = function () {
                        angular.forEach(form, function (field, fieldName) {
                            if (/^[^\$]/.test(fieldName) && angular.isFunction(field.verify)) {
                                field.verify();
                            }
                        });
                        if (!scope.$$phase) {
                            scope.$digest();
                        }
                    };
                    if (!scope.$$phase) {
                        scope.$digest();
                    }
                });
            },
            controller: ['$scope', '$element', '$attrs', function ($scope, $element, $attrs) {
                var entityAndGroupName = $attrs['gValidator'],
                    entityName = entityAndGroupName.split('_')[0],
                    defaultIllegalTarget = $attrs['invalidMsg'];
                return new FormValidatorController({
                    $form: $element,
                    entityName: entityName,
                    defaultIllegalTarget: defaultIllegalTarget
                });
            }]
        }
    };
});